/*
 * Copyright 2010 - 2011, The PLDesktop Development Team
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

#ifndef PLEXCEPTION_BASE_H
#define PLEXCEPTION_BASE_H

#include <setjmp.h>
#if defined __cplusplus
#include <typeinfo>
#include <cxxabi.h>
#include <kernel/PLClassIdFactory.h>
#include <kernel/PLExceptionFactory.h>
using namespace PLSDK;
#else
#include <stdbool.h>
#endif
#include <stddef.h>
#include <PLExceptionList.h>

/**
 * The C structure that was used to transport the data of the exception.
 */
typedef struct {
	/**
	 * The list for the exception.
	 */
	exceptionList_t list;
	/**
	 * The filename of the exception.
	 */
	const char *file;
	/**
	 * The function of the exception.
	 */
	const char *func;
	/**
	 * The line number of the exception.
	 */
	int line;
	/**
	 * The error code or type id of the exception.
	 */
	int errnum;
	/**
	 * The error message of the exception.
	 */
	char *msg;
} plexception_t;

/**
 * Executes end after the block.
 */
#define __exception_end(end) \
	for (int __exception_block_pass = 1; __exception_block_pass; end, __exception_block_pass = 0)

/**
 * Push new environment on the stack.
 */
#define __setjmp_push() do { \
		jmp_buf __tryenv_buffer; \
		int __tryenv_ret = setjmp(__tryenv_buffer); \
		tryenv_push(&__tryenv_buffer, __tryenv_ret); \
	} while (0)

/**
 * executes start before and end after the block.
 */
#define __exception_block(start, end) \
for (int __exception_block_pass2 = 1, __status, start; __exception_block_pass2; end, __exception_block_pass2 = 0)

/**
 * Start new try block.
 *
 * <tt>try</tt> pushes the current environment onto the stack before executing
 * the block. <b>calling <tt>try</tt> without a corresponding <tt>except</tt>
 * will result in undefined behaviour.</b>
 */
#define pltry \
	__setjmp_push(); \
	if(exception_empty()) \
		__exception_end(tryenv_pop())

/**
 * Catch exception.
 *
 * <tt>except</tt> catches an exception and executes the following block. if no
 * <tt>on</tt> block handled the exception control is given to the next
 * environment on the stack. <b>calling <tt>except</tt> without a corresponding
 * <tt>try</tt> will result in undefined behaviour.</b>
 */
#define plexcept \
	else \
		__exception_block(__exception_handled = exception_push(__FILE__, __LINE__, __FUNCTION__, 0, NULL), __exception_rethrow(__exception_handled))

/**
 * Throw a exception in C.
 *
 * @param ... The params for the exception that was generated.
 */
#define plcthrow(...) do { \
		exception_push(__FILE__, __LINE__, __FUNCTION__, __VA_ARGS__); \
		tryenv_jmp(); \
	} while (0)

/**
 * Handle exception.
 *
 * <tt>on</tt> handles the exception <tt>errnum</tt>, passes control to the
 * following block and clears the exception stack afterwards.
 */
#define plon(errnum) \
	if (!__exception_handled && exception_errno() == errnum && (__exception_handled = 1)) \
		__exception_end(exception_clear())

/**
 * Handle unknown exceptions.
 *
 * <tt>finally</tt> handles <em>all</em> exceptions, passes control to the
 * following block and clears the exception stack afterwards.
 */
#define plfinally \
	if (!__exception_handled && (__exception_handled = 1)) \
		__exception_end(exception_clear())

#if defined __cplusplus

/**
 * Catch for a exception class.
 *
 * @param class The class to catch.
 */
#define plcatch(class) \
	if(!__exception_handled && \
		PLExceptionFactory::instance().getTypeIdForKey(exception_errno()) == PLClassIdFactory::instance().getClassId(abi::__cxa_demangle(typeid(class).name(), 0, 0, &__status)) \
		&& (__exception_handled = 1)) \
			__exception_end(exception_clear()) \
				for(class *e = (class*)(PLExceptionFactory::instance().getExceptionForKey(exception_errno())); __exception_block_pass; __exception_block_pass = 0)

#define plthrowEmpty()

/**
 * Throw a exception in C++.
 *
 * @param class The class to throw.
 * @param ... The params for the exception that was generated.
 */
#define plthrow(class, ...) do { \
		int __status; \
		class *exp = new class(__VA_ARGS__); \
		exception_push(__FILE__, __LINE__, __FUNCTION__, PLExceptionFactory::instance().addException( \
						   exp, PLClassIdFactory::instance().getClassId(abi::__cxa_demangle(typeid(*exp).name(), 0, 0, &__status))), exp->what()); \
		tryenv_jmp(); \
	} while (0)

#endif

#if defined __cplusplus
extern "C" {
#endif

/**
 * Create new exception.
 *
 * <tt>exception_push</tt> creates a new exception object and pushes it onto
 * the exception stack.
 * @note this function should not be used directly, <tt>throw</tt> provides
 * better semantics.
 * @param file   source file of this exception
 * @param line   source line of this exception
 * @param func   function where exception was thrown
 * @param errnum <tt>errno</tt> value when this exception was thrown
 * @param fmt    <tt>printf</tt> compatible error message
 * @returns zero
 */
int exception_push(const char *file, int line, const char *func, int errnum, const char *fmt, ...);

/**
 * Check if exceptions exist.
 *
 * <tt>exception_empty</tt> checks whether the exception stack is empty.
 * @note this function should not be used directly,
 * <tt>try</tt>/<tt>except</tt> provide better semantics.
 * @return <tt>true</tt> if the exception stack is empty, <tt>false</tt>
 * otherwise.
 */
bool exception_empty(void);

/**
 * Clear the exception stack.
 *
 * <tt>exception_clear</tt> can be used in <tt>except</tt> blocks to ignore the
 * current exception and clean up the exception stack. failure to clean these
 * stacks while ignoring the thrown exception will result in <b>undefined
 * behaviour.</b>.
 *
 * @note this function should not be used directly.
 * <tt>on</tt>/<tt>finally</tt> provide better semantics.
 */
void exception_clear(void);

/**
 * Get exception errno.
 *
 * <tt>exception_errno</tt> returns the original errno which caused this
 * exception.
 * @note this function should not be used directly, <tt>on</tt> provides better
 * semantics.
 * @return errno value.
 */
int exception_errno(void);

/**
 * Jump to last environment.
 *
 * <tt>tryenv_jmp</tt> jumps to and deletes the topmost environment the stack.
 * @note this function should not be used directly, <tt>throw</tt> provides
 * better semantics.
 */
void tryenv_jmp(void);

/**
 * create new jump environment.
 *
 * <tt>tryenv_push</tt> creates a new <tt>tryenv_t</tt> object for the current
 * jump buffer and pushes it onto the environment stack.
 * 
 * @param env jump environment returned by <tt>setjmp</tt>
 * @param ret return code from <tt>setjmp</tt>
 * @note this function should not be used directly, <tt>try</tt> provides
 * better semantics.
 */
void tryenv_push(jmp_buf *env, int ret);

/**
 * Remove last jump environment.
 *
 * <tt>tryenv_pop</tt> deletes the topmost environment on the environment
 * stack.
 * @note this function should not be used directly, <tt>except</tt> provides
 * better semantics.
 */
void tryenv_pop(void);

/**
 * this cannot be a macro because __exception_block does not allow brace
 * expressions in for loops.
 *
 * @param handled Is the exception handled.
 */
static inline void __exception_rethrow(int handled) {
	if(!handled) {
		tryenv_jmp();
	}
}

#if defined __cplusplus
}
#endif

#endif
